#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include "../myfs.h"

#define MYFS_MIN_BLOCKS 25
//#define MYFS_FIRST_NONMETA_INODE 6

#define MIN(a, b) ((a) < (b) ? (a):(b))

char *progname;

static inline get_block_from_inode(int ino)
{
	return ino;
}

static void create_root_dir(struct myfs_super_block sb, int fd)
{
	int block;
	off_t off;
	char buf[sb.s_blocksize];
	struct myfs_dir_entry dirent;
	struct myfs_inode inode;
	time_t tm;
	memset(&dirent, 0, sizeof(dirent));	/* выделяем память под структуру элемента директории */
	memset(&inode, 0, sizeof(inode));	/* выделяем память под структуру inode */
	memset(buf, 0, sb.s_blocksize);		/* выделяем буфер размером равному размеру блока и заполняем его нулями */

	dirent.inode = MYFS_ROOT_INODE(&sb);
	dirent.name_len = 1;
	dirent.file_type = S_IFDIR;
	strncpy(dirent.name, ".", 1);
	dirent.rec_len = calc_rec_len(&dirent);
	block = get_block_from_inode(dirent.inode);

	/* 
	 * очищаем нужный нам блок
	 */
	off = lseek(fd, block * sb.s_blocksize, SEEK_SET);
	write(fd, buf, sb.s_blocksize); 
	off = lseek(fd, block * sb.s_blocksize, SEEK_SET);
	if (off==-1)
	{
		perror("Unable to create root dirs on device ");
		exit(-1);
	}
	if (write(fd, (char *)&dirent, dirent.rec_len) == -1)
	{
		perror("Unable to write root dirent on device ");
		exit(-1);
	}

	memset(&dirent, 0, sizeof(dirent));
	dirent.inode = MYFS_ROOT_INODE(&sb);
	dirent.name_len = 2;
	dirent.file_type = S_IFDIR;
	strncpy(dirent.name, "..", 2);
	dirent.rec_len = sb.s_blocksize - calc_reclen_from_len(1);
	if (write(fd, (char *)&dirent, sizeof(dirent)) == -1)
	{
		perror("Unable to write root dirent on device ");
		exit(-1);
	}
	printf("Root inode = %u\n", dirent.inode);

	inode.i_uid = inode.i_gid = 0;
	inode.i_size = sb.s_blocksize;
	inode.i_mode = S_IFDIR | 0755;
	inode.i_num_links = 2;
	inode.data[0] = dirent.inode;
	time(&tm);
	inode.atime.tv_sec = inode.mtime.tv_sec = inode.ctime.tv_sec = tm;
	off = 3 * sb.s_blocksize + sizeof(struct myfs_inode) * (dirent.inode - sb.s_first_nonmeta_inode);

	off = lseek(fd, off, SEEK_SET);
	write(fd, buf, sb.s_blocksize); 
	off = lseek(fd, off, SEEK_SET);
	if (off == -1)
	{
		perror("Unable to create root inode on device ");
		exit(-1);
	}
	if (write(fd, (char *)&inode, sizeof(inode)) == -1)
	{
		perror("Unable to write root inode on device ");
		exit(-1);
	}
	return;
}

static void update_bitmaps(struct myfs_super_block sb, int fd)
{
	char buf[sb.s_blocksize];
	char *c;
	int off, i;
	int full, half, full_temp, half_temp, count = 0;
	memset(buf, 0, sb.s_blocksize);		/* выделяем памыть под массив битов, заполняем его нулями */

	//c = buf;
	//*c = 0x7f;	/* 01111111 */

	for(i = 0; i < 4096; i++)
	{
		buf[i] = 0xff;
	}
	
	full = (sb.s_first_nonmeta_inode + 1) / 8;
	half = (sb.s_first_nonmeta_inode + 1) % 8;

	switch(half)
	{
		case 1 :
		{
			buf[full] = 0x01;
			count = 7;
			break;
		}
		case 2 :
		{
			buf[full] = 0x03;
			count = 6;
			break;
		}
		case 3 :
		{
			buf[full] = 0x07;
			count = 5;
			break;
		}
		case 4 :
		{
			buf[full] = 0x0f;
			count = 4;
			break;	
		}
		case 5 :
		{	
			buf[full] = 0x1f;
			count = 3;
			break; 
		}
		case 6 :
		{
			buf[full] = 0x3f;
			count = 2;
			break;
		}
		case 7 :
		{
			buf[full] = 0x7f;
			count = 1;
			break;
		}
	}
	full_temp = full;
	half_temp = half;
	full_temp++;

	full = (sb.s_free_inodes_count - count) / 8;
	half = (sb.s_free_inodes_count - count) % 8;

	for(i = 0; i < full; i++, full_temp++)
	{
		buf[full_temp] = 0x0;
	}

	switch(half)
	{
		case 1 :
		{
			buf[full_temp] = 0xfe;
			break;
		}
		case 2 :
		{
			buf[full_temp] = 0xfc;
			break;
		}
		case 3 :
		{
			buf[full_temp] = 0xf8;
			break;
		}
		case 4 :
		{
			buf[full_temp] = 0xf0;
			break;
		}
		case 5 :
		{
			buf[full_temp] = 0xe0;
			break;
		}
		case 6 :
		{
			buf[full_temp] = 0xc0;
			break;
		}
		case 7 :
		{
			buf[full_temp] = 0x80;
			break;
		}
	}
	
	for(i = 0; i < 4096; i++)
	{
		printf("%d%c|", i, buf[i]);
	}	
	
	off = lseek(fd, 2 * sb.s_blocksize, SEEK_SET); /* устанавливаем указатель на 3-й блок */
	if (off==-1)
	{
		perror("Unable to lseek to bitmap block on device ");
		exit(-1);
	}
	if (write(fd, (char *)buf, sb.s_blocksize) == -1)
	{
		perror("Unable to write bitmap on device ");
		exit(-1);
	}
	return;
}

static void create_myfs(char *device)
{
	int fd;
	off_t off;
	//int total_inodes ;		/* число inode */
	int max_inode_entries;

	int blocks_count;
	int not_used;
	int inode_blocks;
	int free_blocks;
	long max_device_size = 134217728;	

	struct myfs_super_block sb;	/* структура super block для myfs */
	memset(&sb, 0 , sizeof(sb));	/* выделяем память для структуры super block и заполняем её нулями */
	fd = open(device, O_RDWR);
	if (fd==-1)
	{
		perror("Error opening device ");
		exit(-1);
	}

	off = lseek(fd, 0, SEEK_END);	/* устанавливаем указатель в конец файла */
	if (off==-1)
	{
		perror("Error lseeking device ");
		exit(-1);
	}
	if (off / MYFS_DFLT_BLOCKSIZE < MYFS_MIN_BLOCKS)
	{
		fprintf(stderr, "%d", off / MYFS_DFLT_BLOCKSIZE);
		fprintf(stderr, "Too small device file. Atleast 100KB is needed\n");
		exit(-1);
	}
	if(off > max_device_size)
	{
		fprintf(stderr, "Too large device file. 128MB MAX\n");
		exit(-1);
	}

	//total_inodes = off / MYFS_DFLT_BLOCKSIZE;
	blocks_count = off / MYFS_DFLT_BLOCKSIZE;	/* общее количество блоков в файловой системе */
	not_used = off - (blocks_count * MYFS_DFLT_BLOCKSIZE);	/* неиспользуемое пространство */
	inode_blocks = (blocks_count - 3) / 85;		/* количество блоков для и-узлов */
	free_blocks = blocks_count - 3 - inode_blocks;	/* количество свободных блоков */

	sb.s_blocksize = MYFS_DFLT_BLOCKSIZE;		/* устанавливаем размер блока myfs */
	sb.s_magic = MYFS_MAGIC;			/* "магическое число" myfs */
	//sb.s_first_nonmeta_inode = MYFS_FIRST_NONMETA_INODE;
	sb.s_first_nonmeta_inode = 3 + inode_blocks;
	sb.s_inodes_count = blocks_count - sb.s_first_nonmeta_inode;

	max_inode_entries = (inode_blocks * MYFS_DFLT_BLOCKSIZE) / sizeof(struct myfs_inode);
	sb.s_inodes_count = MIN(max_inode_entries, sb.s_inodes_count);
	sb.s_free_inodes_count = sb.s_inodes_count - 1;

	printf("Max number of inodes in filesystem = %u\n", sb.s_inodes_count);
	printf("Not used space in falesystem = %d\n", not_used);
	printf("Inode blocks count in filesystem = %d\n", inode_blocks);

	off = lseek(fd, MYFS_DFLT_BLOCKSIZE, SEEK_SET);
	if (off==-1)
	{
		perror("Error lseeking device ");
		exit(-1);
	}
	if(write(fd, (char *)&sb, sizeof(sb)) == -1)
	{
		perror("Unable to write superblock ");
		exit(-1);
	}

	update_bitmaps(sb, fd);
	create_root_dir(sb, fd);
	close(fd);
}

int main(int argc, char **argv)
{
	char device[50];
	progname = argv[0];
	if (argc < 2)
	{
		fprintf(stderr, "Enter the device name : ");
		scanf("%[^\n]s",device);
	} else
		strcpy(device, argv[1]);
	create_myfs(device);
	return 0;
}
